/*
 * $Log:	MachOFile.m,v $
Revision 1.11  94/05/04  11:19:29  ediger
support for finding which segment a thread's pc is in

Revision 1.10  94/05/01  17:31:33  ediger
added - (struct section *)sectionNamed:: method

Revision 1.9  94/02/07  21:36:27  ediger
Use memory-mapped file object, add segment name display, change
to using a matrix of buttons for choice of what to display.

Revision 1.8  94/01/30  16:26:59  ediger
add threadList List instance var to hold all thread_command load commands
seperately.

Revision 1.7  93/12/20  20:49:20  ediger
better method of transferring load commands from a LoadFVMLibCommand
LoadCommand object to the current MachOFile object's mapped segment list.

Revision 1.6  93/12/18  22:46:51  ediger
changed -initFromFileNamed: method to -fillFromFileNamed: to reflect
a bit more accuratly its function.
Changed the way that subsidiary file's mapped segments get added in
to the mapped segment list.  That process didn't work the old way.

Revision 1.5  93/12/07  23:30:31  ediger
added some assert()s and cpuType and cpuSubtype methods

Revision 1.4  93/12/02  00:44:23  ediger
added fileType method, and a load of comments

Revision 1.3  93/10/31  21:32:13  ediger
ditched the -printSegments method, had been #ifdef'ed out anyway.

Revision 1.2  93/10/27  23:44:09  ediger
Definition of MachOFile object that uses LoadCommand subclasses

 */
#import <MachOFile.h>

@implementation MachOFile

static char rcsident[] = "$Id: MachOFile.m,v 1.11 94/05/04 11:19:29 ediger Exp Locker: ediger $";

//M+	-init
//	PURPOSE:
//	Set up a MachOFile object as much as possible without knowing
//	anything about the file it is to represent.
//
//	EDITORIAL:
//	Assumes that "self" is already allocated.
//M-
- init
{
	[super init];

	mappedSegmentList   = [SortedList alloc];
	unmappedSegmentList = [List alloc];
	threadList          = [List alloc];

	fileHeader    = NULL;
	threadSegment = NULL;

	fileDescriptor = -1;

	mappedFileAddress = (vm_offset_t)0;

	addMappedFiles = FALSE;

	mapsFile = FALSE;

	return self;
}

//M+	-free
//	PURPOSE:
//	Clean up after a MachOFile object as much as possible.
//
//	EDITORIAL:
//M-
- free
{
	if (mapsFile == TRUE && mappedFileAddress != 0)
	{	kern_return_t tRet = vm_deallocate(task_self(),
			(vm_address_t)mappedFileAddress, mappedFileSize);
		if (tRet != KERN_SUCCESS)
		{	lastErrno = 0;
			lastKernReturn = tRet;
		}
	}

	if (fileDescriptor != -1 && close(fileDescriptor) < 0)
	{	lastErrno = errno;
		lastKernReturn = 0;
	}

	fileDescriptor = -1;

	[[mappedSegmentList freeObjects] free];
	[[unmappedSegmentList freeObjects] free];
	[[threadList freeObjects] free];

	mappedSegmentList = NULL;
	unmappedSegmentList = NULL;
	threadList = NULL;

	if (threadSegment) free(threadSegment);
	threadSegment = NULL;

	return [super free];
}

// M+	-initLoadCommandLists:
//	PURPOSE:
//	Set up the sorted lists based on number of commands.
//
//	EDITORIAL:
//	Overallocates.  mapped segment list _and_ unmapped segment list
//	both are set up to have the total number of segments in them.
// M-
-initLoadCommandLists:(int)numberOfCommands
{
	assert(unmappedSegmentList != NULL && mappedSegmentList != NULL);

	[unmappedSegmentList initCount:numberOfCommands];
	[mappedSegmentList   initCount:numberOfCommands];
	[threadList initCount:1];

	[mappedSegmentList   setSortOrder:DESCENDING];
	[mappedSegmentList   setKeyDataType:UNSIGNED_LONG];
	[mappedSegmentList   setKeyMethod:@selector(getBaseAddress)];

	return self;
}

// M+	-addLoadCommand:
//	PURPOSE:
//	Stick a new load command segment into the appropriate SortedList
// M-
-addLoadCommand:oNewSegment
{
	assert(oNewSegment != Nil);

	if ([oNewSegment isMapped])
		[mappedSegmentList addObject:oNewSegment];
	else if ([oNewSegment isThread])
		[threadList addObject:oNewSegment];
	else
		[unmappedSegmentList addObject:oNewSegment];
	return self;
}

// M+	-addLoadCommandsFrom:
//	PURPOSE:
//	Based on a load command that represents a subsidiary file,
//	account for the load commands of that subsidiary file.
// M-
-addLoadCommandsFrom:oNewSegment
{
	id otherFile;

	assert(oNewSegment != Nil);

	otherFile = [[oNewSegment loadOtherFile] otherFile];

	assert(otherFile != Nil);

	if ([otherFile mappedSegments] > 0)
	{	id oOtherFileSegment;

		// Possibly too clever use of the fact that List objects contract
		// dynamically when you take an element out of them.
		while (Nil != (oOtherFileSegment = [otherFile mappedSegment:0]))
		{	[mappedSegmentList addObject:oOtherFileSegment];
			[otherFile removeSegment:oOtherFileSegment];
		}
	}

	return self;
}

//M+	-fillFromFileNamed:
//	PURPOSE:
//	Fill in a MachOFile object now that it is known what file it represents.
//M-
- fillFromFileNamed: (char *) filename
{
	assert(filename != NULL);

	if ([self mapFile:filename])
	{
		mapsFile = TRUE;
		[self fillFromAddress:(char *)mappedFileAddress];
	}

	return self;
}

//M+	-fillFromAddress:
//	PURPOSE:
//	Fill in a MachOFile object whose header is mapped in at the
//	address passed as argument.
//M-
- fillFromAddress: (char *) address
{

	int     iCC, iSegments;
	caddr_t	tAddress;

	assert(address != NULL);

	fileHeader = (struct mach_header *)address;

	// cheesy check for a legit Mach-O file
	if (fileHeader->magic != MH_MAGIC)
	{	[self free];
		return Nil;
	}

	[self initLoadCommandLists:fileHeader->ncmds];

	tAddress = (caddr_t)(fileHeader + 1);

	for (iCC = 0; iCC < fileHeader->ncmds; ++iCC)
	{	id oNewSegment = [LoadCommand new:tAddress];

		tAddress += [oNewSegment commandSize];

		[self addLoadCommand:oNewSegment];

		if (addMappedFiles && [oNewSegment representsMappedFile])
			[self addLoadCommandsFrom:oNewSegment];
	}

	// set up array of thread number to segment number mapping.
	// thread number is index, segment number is value contained in
	// the array at that index.
	if ([self threads])
	{
		threadSegment = (int *)malloc([self threads] * sizeof(int));
		iSegments = [self mappedSegments];
		for (iCC = [self threads] - 1; iCC > -1; --iCC)
		{
			unsigned long ulThreadAddr = [[self thread:iCC] pc];
			int i;
	
			threadSegment[iCC] = -1;

			for (i = 0; i < iSegments; ++i)
			{
				id oSegment = [self mappedSegment:i];
	
				if ([oSegment getBaseAddress] <= ulThreadAddr
					&& [oSegment getUpperAddress] >= ulThreadAddr)
				{
					threadSegment[iCC] = i;
					break;
				}
			}
		}
	}

	return self;
}

//M+	-(int)mapFile:
//	PURPOSE:
//	Perform stuff to memory map the Mach-O file under consideration.
//
//	RETURN:
//	1 on success, 0 on failure.
//
//	EDITORIAL:
//	Apparently you must vm_deallocate() the stuff mapped in by map_fd().
//	Call -free on this object or else.
//M-
- (int)mapFile: (char *)filename
{
	struct stat sStat;
	int         irSuccess = 0;

	assert(filename != NULL);

	if (stat(filename, &sStat) < 0) {
		lastErrno = errno;
		lastKernReturn = 0;
	} else if ((fileDescriptor = open(filename, O_RDONLY)) < 0) {
		lastErrno = errno;
		lastKernReturn = 0;
	} else {
		kern_return_t tRet;

		tRet = map_fd(fileDescriptor, (vm_offset_t)0, &mappedFileAddress, TRUE,
			(size_t)sStat.st_size);

		if (tRet != KERN_SUCCESS) {
			lastErrno = 0;
			lastKernReturn = tRet;
		} else if ((vm_address_t)mappedFileAddress != (vm_address_t)NULL) {

			struct mach_header *mhdr = (struct mach_header *)mappedFileAddress;
			mappedFileSize = mhdr->sizeofcmds;

			tRet = vm_deallocate(task_self(),
				(vm_address_t)mappedFileAddress, (size_t)sStat.st_size);
			if (tRet != KERN_SUCCESS) {
				lastErrno = 0;
				lastKernReturn = tRet;
			} else {

				tRet = map_fd(fileDescriptor, (vm_offset_t)0,
					&mappedFileAddress, TRUE,
					(size_t)mappedFileSize);

				if (tRet != KERN_SUCCESS) {
					lastErrno = 0;
					lastKernReturn = tRet;
				} else
					irSuccess = 1;
			}
		}
	}

	return irSuccess;
}

//M+	-mappedSegments
//	PURPOSE:
//	Return number of load commands that represent stuff memory mapped
//	from a file.
//M-
- (int)mappedSegments
{
	assert(mappedSegmentList != NULL);
	return [mappedSegmentList count];
}

//M+	-unmappedSegments
//	PURPOSE:
//	Return number of load commands that are aren't memory mapped from a file
//M-
- (int)unmappedSegments
{
	assert(unmappedSegmentList != NULL);
	return [unmappedSegmentList count];
}

//M+	-threads
//	PURPOSE:
//	Return number of load commands that represent threads
//M-
- (int)threads
{
	assert(threadList != NULL);
	return [threadList count];
}

//M+	-mappedSegment:
//M-
- mappedSegment:(int)segmentNumber
{
	assert(mappedSegmentList != NULL);
	return [mappedSegmentList objectAt:segmentNumber];
}

//M+	-unmappedSegment:
//M-
- unmappedSegment:(int)segmentNumber
{
	assert(unmappedSegmentList != NULL);
	return [unmappedSegmentList objectAt:segmentNumber];
}

//M+	-thread:
//M-
- thread:(int)threadNumber
{
	assert(threadList != NULL);
	return [threadList objectAt:threadNumber];
}

//M+	-considerMappedFiles
//		Make an instance variable TRUE.  This instance var causes the object
//		to also represent the mapped segments of the subsidiary files
//		referenced by load commands.
//M-
- considerMappedFiles
{
	addMappedFiles = TRUE;
	return self;
}

//M+	-unconsiderMappedFiles
//		Make an instance variable FALSE.  This instance var causes the object
//		to also represent the mapped segments of the subsidiary files
//		referenced by load commands.
//M-
- unconsiderMappedFiles
{
	addMappedFiles = FALSE;
	return self;
}

//M+	MachOFile -removeSegment:aLoadCommand
//		Doesn't ever get called with an "unmapped" load command.
//M-
- removeSegment:aLoadCommand
{
	assert(mappedSegmentList != NULL);
	assert(NX_NOT_IN_LIST != [mappedSegmentList indexOf:aLoadCommand]);
	[mappedSegmentList removeObject:aLoadCommand];
	return self;
}

// M+	-(char *)fileType
//	PURPOSE:
//		Allow for the "type" of Mach-O file that this object represents
//		to be discovered.
//	EDITORIAL:
//		This may be a bit less than generally useful.
//		Should maybe just give back the _number_ of the filetype, lettting
//		something else decide what phrase to use.
// M-
- (char *)fileType
{
	char *pc = NULL;
	static char cBuf[512];

	assert(fileHeader != NULL);

	switch (fileHeader->filetype) {
	case MH_OBJECT:
		pc = "relocatable object file";
		break;
	case MH_EXECUTE:
		pc = "demand paged executable file";
		break;
	case MH_FVMLIB:
		pc = "fixed VM shared library file";
		break;
	case MH_CORE:
		pc = "core file";
		break;
	case MH_PRELOAD:
		pc = "preloaded executable file";
		break;
	default:
		sprintf(cBuf, "Odd file type: 0x%lx\n", fileHeader->filetype);
		pc = cBuf;
		break;
	}

	assert(pc != NULL);

	return pc;
}

// M+	-(char *)cpuType
//	PURPOSE:
//		Allow for the type of cpu that the Mach-O file that this object
//		represents to be discovered.
//	EDITORIAL:
//		This may be a bit less than generally useful.
// M-
- (char *)cpuType
{
	char *pc = NULL;
	static char cBuf[512];

	assert(fileHeader != NULL);

	switch (fileHeader->cputype) {
	case CPU_TYPE_VAX:
		pc = "VAX";
		break;
	case CPU_TYPE_ROMP:
		pc = "ROMP";
		break;
	case CPU_TYPE_NS32032:
		pc = "NS32032";
		break;
	case CPU_TYPE_NS32332:
		pc = "NS32332";
		break;
	case CPU_TYPE_NS32532:
		pc = "NS32532";
		break;
	case CPU_TYPE_MC680x0:
		pc = "MC680x0";
		break;
	case CPU_TYPE_I386:
		pc = "I386";
		break;
	case CPU_TYPE_MIPS:
		pc = "MIPS";
		break;
	case CPU_TYPE_HPPA:
		pc = "HP-PA";
		break;
	case CPU_TYPE_ARM:
		pc = "ARM";
		break;
	case CPU_TYPE_MC88000:
		pc = "MC88000";
		break;
	case CPU_TYPE_SPARC:
		pc = "SPARC";
		break;
	case CPU_TYPE_I860:
		pc = "I860, big-endian";
		break;
	case CPU_TYPE_I860_LITTLE:
		pc = "I860, little-endian";
		break;
	case CPU_TYPE_RS6000:
		pc = "RS/6000";
		break;
	default:
		sprintf(cBuf, "Odd CPU type: 0x%lx\n",
			(unsigned long)fileHeader->cputype);
		pc = cBuf;
		break;
	}

	assert(pc != NULL);

	return pc;
}

// M+	-(char *)cpuSubtype
//	PURPOSE:
//		Allow for the subtype of cpu that the Mach-O file that this object
//		represents to be discovered.
//	EDITORIAL:
//		This may be a bit less than generally useful.
//		Doesn't have all of the cpu subtypes.  680x0 and Intel 80x86 only.
// M-
- (char *)cpuSubtype
{
	char *pc = NULL;
	static char cBuf[512];

	assert(fileHeader != NULL);

	switch (fileHeader->cputype) {
	case CPU_TYPE_MC680x0:
		switch (fileHeader->cpusubtype) {
		case	CPU_SUBTYPE_MC680x0_ALL:
			pc = "all MC680x0";
			break;
		case CPU_SUBTYPE_MC68040:
			pc = "MC68040";
			break;
		case	CPU_SUBTYPE_MC68030_ONLY:
			pc = "MC68030 only";
			break;
		default:
			sprintf(cBuf, "Odd MC680x0 CPU subtype: 0x%lx\n",
				(unsigned long)fileHeader->cpusubtype);
			pc = cBuf;
			break;
		}
		break;

	case CPU_TYPE_I386:
		switch (fileHeader->cpusubtype) {
		case CPU_SUBTYPE_I386_ALL:
			pc = "all i386";
			break;
		case CPU_SUBTYPE_486:
			pc = "80486";
			break;
		default:
			sprintf(cBuf, "Odd I386 CPU subtype: 0x%lx\n",
				(unsigned long)fileHeader->cpusubtype);
			pc = cBuf;
			break;
		}
		break;

	default:
		pc = " ";
		break;
	}

	assert(pc != NULL);

	return pc;
}


//M+
//M-
- (struct section *)sectionNamed:(char *)sectName inSegNamed:(char *)segName
{
	int        iSegments;
	int        iCC;
	struct section *sprAddr = NULL;

	assert(sectName != NULL && segName != NULL);

	iSegments = [self mappedSegments];
	for (iCC = 0; iCC < iSegments && sprAddr == NULL; ++iCC)
	{	id oLdCmd = [self mappedSegment:iCC];

		assert(oLdCmd != Nil);

		if (!strcmp(segName, [oLdCmd commandName]))
		{	int iSect, iNumSects;

			iNumSects = [oLdCmd numberOfSections];
			for( iSect = 0; iSect < iNumSects; ++iSect)
			{	struct section *spSection;

				spSection = [oLdCmd getSection:iSect];
				assert(spSection != NULL);

				if (!strcmp(spSection->sectname, sectName))
				{	sprAddr = spSection;
					break;
				}
			}
		}
	}

	return sprAddr;
}

//M+ - (int)segmentOfThread:(int)threadNumber
//	PURPOSE:
//		return the segment number that contains address of thread
//		threadNumber.
//	RETURN:
//		-1 if the segment number is invalid, or some error occurs.
//		abort() call does something weird in kernel address space.
//		no segment will contain program counter of that thread.
//M-
- (int)segmentOfThread:(int)threadNumber
{
	int irSegNo = -1;

	// not really an error condition if threadSegment is NULL
	if (threadSegment && threadNumber < [self threads])
		irSegNo = threadSegment[threadNumber];

	return irSegNo;
}

@end
